#include "shadows.h"

#include "../../Epsilon/Epsilon3D/OpenGL.h"

#include "../states.h"
#include "../camera.h"    // e3dCamera + e3dMatrix4x4
#include "../console.h"   // eTB_Printf (...)

#include "states.h"
#include "bsp.h"          // eTB_Polygon
#include "projectile.h"   // Temporary, for projectile shadow casters...
#include "render_batch.h" // BatchList

/* For some reason, this MUST be included HERE...
 
     If included earlier, it will cause issues with several MacOS X
       Framework headers.
*/
#include "util.h"         // ApplyEffectTexture (...)

#include "../../Epsilon/timer.h" // eTimer
#include "../../Epsilon/Epsilon3D/viewport.h"

std::vector <eTB_Polygon*>
FindShadowCastingPolys (e3dVector3 light_pos, std::vector <eTB_Polygon*>& polys) {
  e3dVector3 pos = light_pos;

  pos.x = -pos.x;
  pos.y = -pos.y;

//pos.x = 0.0f;
//pos.y = 0.0f;

  std::vector <eTB_Polygon*> casters;

  for (unsigned int i = 0; i < polys.size (); i++) {
    if (polys [i]->getPlane ().ClassifyPoint (pos) == SIDE_FRONT)
      casters.push_back (polys [i]);
  }

  return casters;
}

#include "../prototype/shader.h"
#include "../prototype/program.h"

eTB_ShaderFactory  shader_factory;
eTB_ProgramFactory program_factory;

bool
LinkProgram (eTB_Program* pProgram)
{
  if (! pProgram) {
    eTB_Printf ("Invalid Program Passed...\n");
    return false;
  }

  if (pProgram->Link ())
    return true;

  eTB_Printf (" >> Error Linking Program '%s' << \n", pProgram->GetName ());
  eTB_Printf ("%s\n", pProgram->GetLog ().CString ());

  return false;
}

bool
CompileAndAttach (eTB_Shader* pShader, eTB_Program* pProgram)
{
  if (! pShader) {
    eTB_Printf ("Invalid Shader Passed...\n");
    return false;
  }

  if (! pProgram) {
    eTB_Printf ("Invalid Program Passed...\n");
    return false;
  }

  if (pShader->Compile ()) {
    if (pProgram->AttachShader (pShader))
      return true;
    else {
      eTB_Printf (" >> Error Attaching '%s' <<\n", pShader->name_);
      eTB_Printf ("%s\n", pProgram->GetLog ().CString ());
    }
  } else {
    eTB_Printf (" >> Error Compiling '%s' <<\n", pShader->name_);
    eTB_Printf ("%s\n", pShader->GetLog ().CString ());
  }

  return false;
}

class eTB_ShadowProjector {
public:
  virtual bool init (void) = 0;
};

/* Omni-Directional */
/* ---------------- */

class eTB_CubicShadowProjector {
};

class eTB_DualParaboloidShadowProjector {
};

/* Directional */
/* ----------- */

class eTB_DirectionalShadowProjector {
};

class eTB_ShadowCube : public eTB_ShadowProjector {
public:
  eTB_ShadowCube (void) {
    build_depth_cube = NULL;
    bdc_ps           = NULL;
    bdc_vs           = NULL;

    cast_depth_cube  = NULL;
    cdc_ps           = NULL;
    cdc_vs           = NULL;
  }

  bool init (void) {
    if (build_depth_cube == NULL) {
      build_depth_cube =
        program_factory.createProgram ("Depth Cube Builder");

      bdc_ps =
        shader_factory.createShaderFromFile (_T ("build_depth_cube_pix.glsl"), "ps::build_depth_cube", eTB_Shader::Pixel);

      bdc_vs =
        shader_factory.createShaderFromFile (_T ("build_depth_cube_vtx.glsl"), "vs::build_depth_cube", eTB_Shader::Vertex);

      CompileAndAttach (bdc_ps, build_depth_cube);
      CompileAndAttach (bdc_vs, build_depth_cube);

      LinkProgram (build_depth_cube);
    }

    if (cast_depth_cube == NULL) {
      cast_depth_cube =
        program_factory.createProgram ("Cubemap Shadow Caster");

      cdc_ps =
        shader_factory.createShaderFromFile (_T ("omni_cube_shadow_ps.glsl"), "ps:omni_cube_shadow", eTB_Shader::Pixel);

      cdc_vs =
        shader_factory.createShaderFromFile (_T ("omni_cube_shadow_vs.glsl"), "vs::omni_cube_shadow", eTB_Shader::Vertex);

      CompileAndAttach (cdc_ps, cast_depth_cube);
      CompileAndAttach (cdc_vs, cast_depth_cube);

      LinkProgram (cast_depth_cube);
    }

    return true;
  }

  eTB_Program* build_depth_cube;
  eTB_Shader*  bdc_ps;
  eTB_Shader*  bdc_vs;

  eTB_Program* cast_depth_cube;
  eTB_Shader*  cdc_ps;
  eTB_Shader*  cdc_vs;
};

class eTB_ShadowParaboloid : public eTB_ShadowProjector {
public:
  eTB_ShadowParaboloid (void) {
    build_depth_hemi = NULL;
    bdh_ps           = NULL;
    bdh_vs           = NULL;

    cast_depth_hemi  = NULL;
    cdh_ps           = NULL;
    cdh_vs           = NULL;
  }

  bool init (void) {
    if (build_depth_hemi == NULL) {
      build_depth_hemi =
        program_factory.createProgram ("Paraboloid Depth Map Builder");

      bdh_ps =
        shader_factory.createShaderFromFile (_T ("shaders/paraboloid_shadow/build_ps.glsl"), "ps::build_paraboloid", eTB_Shader::Pixel);

      bdh_vs =
        shader_factory.createShaderFromFile (_T ("shaders/paraboloid_shadow/build_vs.glsl"), "vs::build_paraboloid", eTB_Shader::Vertex);

      CompileAndAttach (bdh_ps, build_depth_hemi);
      CompileAndAttach (bdh_vs, build_depth_hemi);

      LinkProgram (build_depth_hemi);
    }

    if (cast_depth_hemi == NULL) {
      cast_depth_hemi =
        program_factory.createProgram ("Paraboloid Depth Map Projector");

      cdh_ps =
        shader_factory.createShaderFromFile (_T ("shaders/paraboloid_shadow/project_ps.glsl"), "ps::paraboloid_shadow_proj", eTB_Shader::Pixel);

      cdh_vs =
        shader_factory.createShaderFromFile (_T ("shaders/paraboloid_shadow/project_vs.glsl"), "vs::paraboloid_shadow_proj", eTB_Shader::Vertex);

      CompileAndAttach (cdh_ps, cast_depth_hemi);
      CompileAndAttach (cdh_vs, cast_depth_hemi);

      LinkProgram (cast_depth_hemi);
    }

    return true;
  }

  eTB_Program* build_depth_hemi;
  eTB_Shader*  bdh_ps;
  eTB_Shader*  bdh_vs;

  eTB_Program* cast_depth_hemi;
  eTB_Shader*  cdh_ps;
  eTB_Shader*  cdh_vs;
};


/* Singletons for now... */
eTB_ShadowCube       cubemap_shadow_projector;
eTB_ShadowParaboloid paraboloid_shadow_projector;

void
ReloadShadowShaders (void)
{
  /* This is a bad idea, since it causes a memory leak... */
    // THIS IS ONLY A TEMPORARY SOLUTION!
  cubemap_shadow_projector.build_depth_cube = NULL;
  cubemap_shadow_projector.cast_depth_cube  = NULL;

  cubemap_shadow_projector.init ();

  paraboloid_shadow_projector.build_depth_hemi = NULL;
  paraboloid_shadow_projector.cast_depth_hemi  = NULL;

  paraboloid_shadow_projector.init ();
}

e3dMatrix4x4 lightProjectionMatrix;
e3dMatrix4x4 lightViewMatrix;
e3dMatrix4x4 biasMatrix;

int shadow_res_x;
int shadow_res_y;

#include "../window.h"

#define DIFFERENT_FOV


GLuint
InitShadows (void)
{
  const int shadow_map_res = 512;

  static bool init       = false;
  static GLuint  shadow_tex = 0;

#ifndef DIFFERENT_FOV
  /* Re-Initialize if the Size Changes. */
  if (shadow_res_x != ::states->window->viewport.Width  () ||
      shadow_res_y != ::states->window->viewport.Height ()) {
    if (init)
      glDeleteTextures (1, &shadow_tex);
    init = false;
  }

  shadow_res_x = ::states->window->viewport.Width  ();
  shadow_res_y = ::states->window->viewport.Height ();
#else
  shadow_res_x = 768;
  shadow_res_y = 768;
#endif

  if (init) {
    return shadow_tex;
  }

  //cubemap_shadow_projector.init ();

  // Bias from [-1, 1] to [0, 1]
  biasMatrix.m [ 0] = 0.5f; biasMatrix.m [ 1] = 0.0f; biasMatrix.m [ 2] = 0.0f; biasMatrix.m [ 3] = 0.5f;
  biasMatrix.m [ 4] = 0.0f; biasMatrix.m [ 5] = 0.5f; biasMatrix.m [ 6] = 0.0f; biasMatrix.m [ 7] = 0.5f;
  biasMatrix.m [ 8] = 0.0f; biasMatrix.m [ 9] = 0.0f; biasMatrix.m [10] = 0.5f; biasMatrix.m [11] = 0.5f;
  biasMatrix.m [12] = 0.0f; biasMatrix.m [13] = 0.0f; biasMatrix.m [14] = 0.0f; biasMatrix.m [15] = 1.0f;

  //Create the shadow map texture
  glGenTextures (1,             &shadow_tex);
  glBindTexture (GL_TEXTURE_2D,  shadow_tex);
#ifdef DEBUG_SHADOWS
  glTexImage2D  ( GL_TEXTURE_2D, 0, GL_RGB, shadow_res_x, shadow_res_y, 0,
                  GL_RGB, GL_UNSIGNED_BYTE, NULL);
#else
  glTexImage2D  ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadow_res_x, shadow_res_y, 0,
                  GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
#endif

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

#ifndef DEBUG_SHADOWS
 //Enable shadow comparison
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
 //Shadow comparison should be true (ie not in shadow) if r<=texture
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
 //Shadow comparison should generate an INTENSITY result
 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
 glDisable (GL_TEXTURE_2D);
#endif

  if (shadow_tex != 0)
    init = true;

  return shadow_tex;
}

#include "room.h"

void
OpenGLCheckPoint (const char* szDescription)
{
  GLuint error = glGetError ();
  if (error != GL_NO_ERROR) {
    eTB_Printf ("OpenGL Error : %x (%d)\n - %s\n", error, error, szDescription);
  }
}

enum DIRECTIONS {
  NORTH = 0,
  SOUTH = 1,
  EAST  = 2,
  WEST  = 3,
  UP    = 4,
  DOWN  = 5
};

/* Directional Light... */
void
BeginShadows (e3dVector3 light_pos, Underlight::Room* room)
{
  glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT | GL_VIEWPORT_BIT); // Cull Face...

  /* The Vertex Shader uses the Position of GL_LIGHT1... */
  glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
  glEnable  (GL_LIGHT1);
//  glEnable  (GL_LIGHTING);

  cubemap_shadow_projector.init ();
  cubemap_shadow_projector.build_depth_cube->Bind ();

#ifdef DIFFERENT_FOV
  glViewport (0, 0, shadow_res_x, shadow_res_y);
#endif

  glEnable        (GL_POLYGON_OFFSET_FILL);
//  glPolygonOffset (1.1f, 40.0f);
//  glClearDepth (1.0f);

  glEnable (GL_POLYGON_OFFSET_FILL);
  glPolygonOffset (0.9f, 5.0f);

  //glClear (GL_DEPTH_BUFFER_BIT);

  int shadow_tex = InitShadows ();

////////    GLfloat lightProjMat [16];
    GLfloat lightViewMat [16];

    /* Only works for the case that the player is holding a spotlight straight out... */
/////    glLoadMatrixf (cli_parse::states->cl_player.Camera ()->GetModelview ());

#if 1
    glMatrixMode   (GL_MODELVIEW);
    glPushMatrix   ();
    glLoadIdentity ();

    float scale = 100.0f;

    e3dVector3 fwd = cli_parse::states->cl_player.Camera ()->fwd_vec ();

    e3dVector3 pos = cli_parse::states->cl_player.Camera ()->GetPos ();


//   light_pos = pos;

//   pos.x += (fwd.x * 100.0f);
//   pos.y += (fwd.y * 100.0f);
//   pos.z += (fwd.z * 100.0f);

//    pos.x = room->bound.Center ().x;
//   pos.y = room->bound.Center ().y;
//   pos.z = 32.0f;

    e3dVector3 up = cli_parse::states->cl_player.Camera ()->up_vec ();

    up.x = 0.0f; up.y = 0.0f; up.z = 1.0f;

    e3dVector3 center (0.0f, 0.0f, 0.0f);

    if (cli_parse::GetNumProjectiles ()) {
      int projectile_id = cli_parse::GetNumProjectiles () - 1;
      cli_parse::Projectile* proj = cli_parse::GetProjectile (projectile_id);

      center  = proj->origin;
      //center += (proj->velocity * 2.0f);
    }

    static int   side = 0;
    static float side_update = 0.0f;

    static eTimer timer;
    timer.tick ();

    if (side > 5) {
      side = 0;
    }

    switch (side) {
      case 0:
        center += e3dVector3 (1.0f, 0.0f, 0.0f);
        break;
      case 1:
        center -= e3dVector3 (1.0f, 0.0f, 0.0f);
        break;
      case 2:
        center += e3dVector3 (0.0f, 1.0f, 0.0f);
        break;
      case 3:
        center -= e3dVector3 (0.0f, 1.0f, 0.0f);
        break;
      case 4:
        center += e3dVector3 (0.0f, 0.0f, 1.0f);
        up      = e3dVector3 (0.0f, 1.0f, 0.0f);
        break;
      case 5:
        center -= e3dVector3 (0.0f, 0.0f, 1.0f);
        up      = e3dVector3 (0.0f, 1.0f, 0.0f);
        break;
    }

    const float side_interval = 1000.0f;

    if ((float)timer.milliseconds () - side_update > side_interval) {
      side++;
      side_update = (float)timer.milliseconds ();
    }

//    center += e3dVector3 (1.0f, 0.0f, 0.0f);

    gluLookAt( light_pos.x, light_pos.y, light_pos.z,
      center.x, center.y, center.z,
      up.x, up.y, up.z);
    glGetFloatv (GL_MODELVIEW_MATRIX, lightViewMat);

    lightViewMatrix       = e3dMatrix4x4 (lightViewMat);
#else
    const float scale = -100.0f;
    e3dVector3 pos = cli_parse::states->cl_player.Camera ()->GetPos ();
    //pos.x += scale * cli_parse::states->cl_player.Camera ()->fwd_vec ().x;
    //pos.y += scale * cli_parse::states->cl_player.Camera ()->fwd_vec ().y;
    //pos.z += scale * cli_parse::states->cl_player.Camera ()->fwd_vec ().z;
    pos = light_pos;
    e3dCamera new_cam = *cli_parse::states->cl_player.Camera ();
    new_cam.SetPos (pos.x, pos.y, pos.z);
    lightViewMatrix = e3dMatrix4x4 (new_cam.GetModelview ());
    glPushMatrix ();
    glLoadMatrixf (new_cam.GetModelview ());
//    lightViewMatrix = e3dMatrix4x4 (cli_parse::states->cl_player.Camera ()->GetModelview ());
#endif

    const float white [4] = { 0.0f, 0.0f, 0.0f, 1.0f };

    //Use the color as the ambient and diffuse material
//    glColorMaterial (GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
//    glEnable (GL_COLOR_MATERIAL);

    //White specular material color, shininess 16
//    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white);
//    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 16.0f);


////    glMatrixMode (GL_PROJECTION);
////    glPushMatrix ();

////    glLoadMatrixf (lightProjMat);


    GLfloat light_proj_mat [16];

#ifndef DIFFERENT_FOV
    glGetFloatv (GL_PROJECTION_MATRIX, light_proj_mat);
#else
    glMatrixMode   (GL_MODELVIEW);
    glPushMatrix   ();
    glLoadIdentity ();
    float aspect = (float)shadow_res_x / (float)shadow_res_y;
    gluPerspective (90.0f, aspect, 1.0f, 8000.0f);
    glGetFloatv    (GL_MODELVIEW_MATRIX, light_proj_mat);
    glPopMatrix    ();
    glMatrixMode (GL_PROJECTION);
    glPushMatrix ();
      glLoadMatrixf (light_proj_mat);
#endif

    lightProjectionMatrix = e3dMatrix4x4 (light_proj_mat);

    glEnable    (GL_CULL_FACE);
//    glDisable    (GL_ALPHA_TEST);
    glCullFace   (GL_FRONT);
    glDepthMask  (GL_TRUE);
#ifndef DEBUG_SHADOWS
    glColorMask  (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
#endif
    glShadeModel (GL_FLAT);

//    glBindTexture (GL_TEXTURE_2D, shadow_tex);
//    glDisable     (GL_TEXTURE_2D);
}

void
EndShadows (void)
{
  GLuint shadow_tex = InitShadows ();

  //// Assumes the depth texture is already bound (ShadowPass must not change this...)
  glBindTexture       (GL_TEXTURE_2D, shadow_tex);

  glFinish  ();
  glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 0, 0, shadow_res_x, shadow_res_y);

  glClear (GL_DEPTH_BUFFER_BIT);

#ifdef DIFFERENT_FOV
  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();
#endif

////  glMatrixMode (GL_MODELVIEW);
////  glPopMatrix  ();

#if 0
  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();
#endif

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  cubemap_shadow_projector.build_depth_cube->Unbind ();

  glPopAttrib  ();

//  glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}

void
ShadowPass (eTB_Polygon* poly, e3dVector3 light_pos)
{
  glBegin (GL_TRIANGLE_FAN);

    e3dVector3 v1 (*poly->getVertex (0));
    e3dVector3 v2 (*poly->getVertex (1));
    e3dVector3 v3 (*poly->getVertex (2));
    e3dVector3 v4 (*poly->getVertex (3));

    glVertex3f (v1.x, v1.y, v1.z);
    glVertex3f (v2.x, v2.y, v2.z);
    glVertex3f (v3.x, v3.y, v3.z);
    glVertex3f (v4.x, v4.y, v4.z);

  glEnd ();
}

#include "../font.h"
#include "../../Epsilon/Epsilon3D/texture.h"
extern eFont* font;

extern void
DrawShadowActors ( Underlight:: Room* room,
                   const float&       time,
                   bool               texture = true );

/* TODO: Pass e3dLight, instead of light_pos...
           The type of shadow to cast can be determined by the type of
             e3dLight passed. */
void
ShadowPass_Build ( Underlight::Room* room,
                   const float&      time,
                   e3dVector3        light_pos )
{
  eTB_ShadowProjector* projector = NULL;
  eTB_Program*         builder   = NULL;

//  projector = &cubemap_shadow_projector;

//  projector->init ();
//  builder = ((eTB_ShadowCube *)projector)->build_depth_cube;

//  for (int i = 0; i < 6; i++) {
//    BeginOmniShadows (light_pos, room, i);

  /* TODO: eTB_ShadowProjector::GetBuilder (...) */
  for (int i = 0; i < 1; i++) {
    glClear (GL_DEPTH_BUFFER_BIT);
    projector = &paraboloid_shadow_projector;
    builder   = ((eTB_ShadowParaboloid *)projector)->build_depth_hemi;

    BeginDualShadows (light_pos, room, i);

//    builder->Bind ();

    /* Optimization: Depth-Only Pre-Pass... NOTE: Should NOT include pixel shader. */
    glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    DrawShadowBatches (room, time, false, false);
    DrawShadowBatches (room, time, true,  true);

    builder->Bind ();

    /* Second pass, only write to the color buffer where fragments actually pass a
         depth-test. This is necessary since we cannot directly write the DEPTH buffer
           to a cubemap face. We must encode the depth into an RGB/RGBA integer texture,
             using the RGB/A color components. */
    glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    DrawShadowBatches (room, time, false, false);
    DrawShadowBatches (room, time, true,  true);

#ifdef ACTORS_CAST_SHADOWS
      glEnable         (GL_TEXTURE_2D);
      DrawShadowActors (room, time);
      glDisable        (GL_TEXTURE_2D);
#endif

    builder->Unbind ();

    EndDualShadows (i);

    //EndOmniShadows (i);
  }

  /* Temp Hack. Fix this later... */
  glEnable  (GL_TEXTURE_2D);
  glDisable (GL_TEXTURE_CUBE_MAP);
}

void
ShadowPass_Cast ( Underlight::Room* room,
                  const float&      time,
                  e3dVector3        light_pos )
{
//  BeginDualShadowPostPass (light_pos);

//    paraboloid_shadow_projector.cast_depth_hemi->Bind ();

//    DrawShadowBatches (room, time, false, false);
//    DrawShadowBatches (room, time, false, true);

//    paraboloid_shadow_projector.cast_depth_hemi->Unbind ();

//  extern void EndDualShadowPostPass (void);
//  EndDualShadowPostPass ();

/*
  BeginOmniShadowPostPass (light_pos);

    cubemap_shadow_projector.cast_depth_cube->Bind ();

    DrawShadowBatches (room, time, false, false);
    DrawShadowBatches (room, time, false, true);

    cubemap_shadow_projector.cast_depth_cube->Unbind ();

  extern void EndOmniShadowPostPass (void);
  EndOmniShadowPostPass ();
*/

#if 0
//#ifdef DEBUG_SHADOWS
//        extern GLuint InitShadows (void);
        glDisable (GL_DEPTH_TEST);
        glDisable (GL_CULL_FACE);
        glDisable (GL_ALPHA_TEST);
        glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        extern GLuint InitDualShadows (int side);
        extern void   DrawScreenSpaceQuadEx (GLuint texture, float scale);
        glColor4f (1.0f, 1.0f, 1.0f, 1.0f);
        DrawScreenSpaceQuadEx (InitDualShadows (0), 1.0f);
        glEnable (GL_DEPTH_TEST);
//        extern GLuint InitOmniShadows (void);
//        extern void   DrawScreenSpaceQuadCubeEx (GLuint texture, float scale);
//        DrawScreenSpaceQuadCubeEx (InitOmniShadows (), 1.0f);
//#endif
#endif
}

void
DrawShadowBatches ( Underlight::Room* room,
                    const float&      time,
                    bool              apply_tex,
                    bool              animated )
{
  BatchList& batches = ( animated ? room->animated_batches :
                                    room->batches );

  std::vector<cli_parse::RenderBatch*>::const_iterator batch      =
      batches.begin ();
  std::vector<cli_parse::RenderBatch*>::const_iterator last_batch =
      batches.end   ();

  while (eSTL::IterValid (batch, last_batch)) {
    if (animated) {
      if ((*batch)->animated == 0) {
        ++batch;
        continue;
      }
    } else {
      if ((*batch)->animated != 0) {
        ++batch;
        continue;
      }
    }

    // TEMP: Debug Cube Map
    apply_tex = true;

   if (! apply_tex) {
     glDisable            (GL_TEXTURE_2D);
     glDisableClientState (GL_TEXTURE_COORD_ARRAY);
   } else {
     glEnableClientState  (GL_TEXTURE_COORD_ARRAY);
#ifdef DEBUG_SHADOWS
      extern void SetupTextures (cli_parse::RenderBatch*, const float&);
      glEnable      (GL_TEXTURE_2D);
      SetupTextures ((*batch), time);
#endif
   }

    room->vcache->DrawIndexed ((*batch)->primitive_type, (*batch)->icache, 0, (unsigned int)(*batch)->icache->GetSize ());

    ++batch;
  }
}


#define g_Window ::states->window

void
DrawScreenSpaceQuad (float scale, float r, float g, float b, float a)
{
  struct OverlayVertex
  {
    float pos [2];
    float st  [2];
  };

  OverlayVertex aOverlayVertices [4];

  OverlayVertex* pVert = aOverlayVertices;

  pVert [0].pos [0] = 0.0f;
  pVert [0].pos [1] = 0.0f;
  pVert [0].st  [0] = 0.0f;
  pVert [0].st  [1] = 1.0f;

  pVert [1].pos [0] = 0.0f;
  pVert [1].pos [1] = ::states->window->viewport.Height () * scale;
  pVert [1].st  [0] = 0.0f;
  pVert [1].st  [1] = 0.0f;

  pVert [2].pos [0] = ::states->window->viewport.Width () * scale;
  pVert [2].pos [1] = 0.0f;
  pVert [2].st  [0] = 1.0f;
  pVert [2].st  [1] = 1.0f;

  pVert [3].pos [0] = ::states->window->viewport.Width  () * scale;
  pVert [3].pos [1] = ::states->window->viewport.Height () * scale;
  pVert [3].st  [0] = 1.0f;
  pVert [3].st  [1] = 0.0f;

  glPushAttrib (GL_TRANSFORM_BIT | GL_ENABLE_BIT);
  glPushClientAttrib (GL_CLIENT_VERTEX_ARRAY_BIT);
//  glPushAttrib       (GL_ALL_ATTRIB_BITS);
//  glPushClientAttrib (GL_CLIENT_ALL_ATTRIB_BITS);

  glDepthMask        (GL_FALSE);

  glMatrixMode   (GL_MODELVIEW);
  glPushMatrix   ();
  glLoadIdentity ();

  glMatrixMode   (GL_PROJECTION);
  glPushMatrix   ();
  glLoadIdentity ();

  glOrtho (0, g_Window->viewport.Width (), 0, g_Window->viewport.Height (), -1.0f, 1.0f);

  pVert = aOverlayVertices;


  //glDisable (GL_DEPTH_TEST);
  glDisable (GL_CULL_FACE);

  //cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
  //cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

  glDisable (GL_COLOR_MATERIAL);
//  glDisable (GL_TEXTURE_2D);

  glNormal3f (0.0f, 0.0f, 1.0f);
  glColor4f (r, g, b, a);

  glVertexPointer   (2, GL_FLOAT, sizeof (OverlayVertex), &pVert [0].pos);
  glTexCoordPointer (2, GL_FLOAT, sizeof (OverlayVertex), &pVert [0].st);

  glDisableClientState (GL_COLOR_ARRAY);
  glEnableClientState  (GL_VERTEX_ARRAY);
  glEnableClientState  (GL_TEXTURE_COORD_ARRAY);

  glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  glPopAttrib       ();
  glPopClientAttrib ();
}

void
DrawScreenSpaceQuadEx (GLuint texture, float scale)
{
  glBindTexture (GL_TEXTURE_2D, texture);
  glEnable      (GL_TEXTURE_2D);

  DrawScreenSpaceQuad (scale);
}

void
DrawScreenSpaceQuadCubeEx (GLuint texture, float scale)
{
  glBindTexture (GL_TEXTURE_CUBE_MAP, texture);
  glEnable      (GL_TEXTURE_CUBE_MAP);

  glPushAttrib (GL_TEXTURE_BIT);

  glTexGenf (GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGenf (GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGenf (GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glEnable   (GL_TEXTURE_GEN_S);
  glEnable   (GL_TEXTURE_GEN_T);
  glEnable   (GL_TEXTURE_GEN_R);

  DrawScreenSpaceQuad (scale);

  glPopAttrib ();

  glDisable    (GL_TEXTURE_CUBE_MAP);
}

void
DrawBackground (int tex, float r, float g, float b, float a)
{
  glPushAttrib (GL_POLYGON_BIT);
    glDepthMask         (GL_FALSE);
    glPolygonMode       (GL_FRONT_AND_BACK, GL_FILL);
    ApplyEffectTexture  (tex);
    DrawScreenSpaceQuad (1.0f, r, g, b, a);
    glDepthMask         (GL_TRUE);
  glPopAttrib ();
}

e3dMatrix4x4
TransposeMat (e3dMatrix4x4 mat) {
  e3dMatrix4x4 ret;

  ret.m11 = mat.m11; ret.m12 = mat.m21; ret.m13 = mat.m31; ret.m14 = mat.m41;
  ret.m21 = mat.m12; ret.m22 = mat.m22; ret.m23 = mat.m32; ret.m24 = mat.m42;
  ret.m31 = mat.m13; ret.m32 = mat.m23; ret.m33 = mat.m33; ret.m34 = mat.m43;
  ret.m41 = mat.m14; ret.m42 = mat.m24; ret.m43 = mat.m34; ret.m44 = mat.m44;

  return ret;
}

void
BeginShadowPostPass (e3dVector3 light_pos)
{
    glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT); // Cull Face...

    static float white [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float grey  [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float black [4] = { 0.0f, 0.0f, 0.0f, 1.0f };

    glDepthMask (GL_FALSE);

    glColorMaterial      (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glColor4f            (0.1f, 0.1f, 0.1f, 1.0f);
    glEnable (GL_COLOR_MATERIAL);

 //Use dim light to represent shadowed areas
    glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
    glLightfv (GL_LIGHT1, GL_AMBIENT, grey);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, black);
    glDisable  (GL_LIGHT0);
    glEnable   (GL_LIGHT1);
    glEnable   (GL_LIGHTING);

    //3rd pass
    //Draw with bright light
    glLightfv (GL_LIGHT1, GL_DIFFUSE,  white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, white);


    //Calculate texture matrix for projection
    //This matrix takes us from eye space to the light's clip space
    //It is postmultiplied by the inverse of the current view matrix when specifying texgen

    /// TODO: Implement e3dMatrix4x4 construction from float params...

    static float tex_mat [16];

    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
      glLoadTransposeMatrixf (lightViewMatrix.m);
      glMultTransposeMatrixf (lightProjectionMatrix.m);
      glMultMatrixf (biasMatrix.m);
      glGetFloatv       (GL_MODELVIEW_MATRIX, tex_mat);
   glPopMatrix ();

   e3dMatrix4x4 textureMatrix (tex_mat);

//    e3dMatrix4x4 textureMatrix = biasMatrix * lightProjectionMatrix * lightViewMatrix;

//    textureMatrix = TransposeMat (textureMatrix);

    GLuint shadow_tex = InitShadows ();

    //Bind & enable shadow map texture
    glBindTexture (GL_TEXTURE_2D, shadow_tex);
    glEnable      (GL_TEXTURE_2D);

#if 1
    //Set up texture coordinate generation.
    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_S, GL_EYE_PLANE, textureMatrix.Row (0));
    glEnable   (GL_TEXTURE_GEN_S);

    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_T, GL_EYE_PLANE, textureMatrix.Row (1));
    glEnable   (GL_TEXTURE_GEN_T);

    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_R, GL_EYE_PLANE, textureMatrix.Row (2));
    glEnable   (GL_TEXTURE_GEN_R);

    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_Q, GL_EYE_PLANE, textureMatrix.Row (3));
    glEnable   (GL_TEXTURE_GEN_Q);
#else
    //Set up texture coordinate generation.
//    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_S);

//    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_T);

//    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_R);

//    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_Q);
#endif

#ifndef DEBUG_SHADOWS
    //Enable shadow comparison
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);

    //Shadow comparison should be true (ie not in shadow) if r<=texture
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL);

    //Shadow comparison should generate an INTENSITY result
    glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
#endif

    //Set alpha test to discard false comparisons
    glAlphaFunc (GL_GEQUAL, 0.99f);
    glEnable    (GL_ALPHA_TEST);

    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendColor (0.33f, 0.33f, 0.33f, 1.0f);
    glBlendFunc (GL_DST_COLOR, GL_CONSTANT_COLOR);
//    glBlendFunc (GL_ZERO, GL_ZERO);
    glEnable    (GL_BLEND);

//    glDepthFunc (GL_LESS);
}

void
EndShadowPostPass (void)
{
  glPopAttrib ();

  glDepthMask (GL_TRUE);
  glDisable   (GL_CLIP_PLANE0);

  GLuint error = glGetError ();
  if (error != GL_NO_ERROR) {
    eTB_Printf ("OpenGL Error : %x (%d)\n", error, error);
  }
}

GLuint
InitDualShadows (int side)
{
  const int shadow_map_res = 512;

  static bool init_1       = false;
  static bool init_2       = false;
  static GLuint  shadow_tex_1 = 0;
  static GLuint  shadow_tex_2 = 0;

  bool&   init        = (side == 0 ? init_1       : init_2);
  GLuint& shadow_tex  = (side == 0 ? shadow_tex_1 : shadow_tex_2);

#if 0
//#ifndef DIFFERENT_FOV
  /* Re-Initialize if the Size Changes. */
  if (shadow_res_x != ::states->window->viewport.Width  () ||
      shadow_res_y != ::states->window->viewport.Height ()) {
    if (init)
      glDeleteTextures (1, &shadow_tex);
    init = false;
  }

  shadow_res_x = ::states->window->viewport.Width  ();
  shadow_res_y = ::states->window->viewport.Height ();
#else
  shadow_res_x = 768;
  shadow_res_y = 768;
#endif

  if (init) {
    return shadow_tex;
  }

  //Create the shadow map texture
  glGenTextures (1,             &shadow_tex);
  glBindTexture (GL_TEXTURE_2D,  shadow_tex);

//#ifdef DEBUG_SHADOWS
  glTexImage2D  ( GL_TEXTURE_2D, 0, GL_RGB, shadow_res_x, shadow_res_y, 0,
                  GL_RGB, GL_UNSIGNED_BYTE, NULL);
//#else
//  glTexImage2D  ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadow_res_x, shadow_res_y, 0,
//                  GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
//#endif

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  if (shadow_tex != 0)
    init = true;

  return shadow_tex;
}

/* Dual Paraboloid */
void
BeginDualShadows (e3dVector3 light_pos, Underlight::Room* room, int side)
{
  glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT | GL_VIEWPORT_BIT); // Cull Face...

  glEnable (GL_NORMALIZE);
  /* The Vertex Shader uses the Position of GL_LIGHT1... */
  glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
  glEnable  (GL_LIGHT1);
//  glEnable  (GL_LIGHTING);

  // Bind Shader

  glViewport (0, 0, shadow_res_x, shadow_res_y);

  glEnable        (GL_POLYGON_OFFSET_FILL);

  glEnable (GL_POLYGON_OFFSET_FILL);
  glPolygonOffset (1.1f, 4.0f);

  int shadow_tex = InitDualShadows (side);

//    GLfloat lightProjMat   [16];
//    GLfloat lightViewMat   [16];
//    GLfloat light_proj_mat [16];

    glMatrixMode   (GL_PROJECTION);
    glPushMatrix   ();
    glLoadIdentity ();

    gluPerspective (45.0f, 1.0f, 0.1f, 40000.0f);

    glMatrixMode   (GL_MODELVIEW);
    glPushMatrix   ();
//    glLoadIdentity ();

    e3dVector3 up;
    e3dVector3 center;

    up = cli_parse::states->cl_player.Camera ()->up_vec ();

    if (cli_parse::GetNumProjectiles ()) {
      int projectile_id = cli_parse::GetNumProjectiles () - 1;
      cli_parse::Projectile* proj = cli_parse::GetProjectile (projectile_id);

      center  = proj->origin;

     // if (side == 0)
       // center += (proj->velocity * 2.0f);
      //else
        //center -= (proj->velocity * 2.0f);

      up += proj->origin;
    }

    e3dCamera camera (*cli_parse::states->cl_player.Camera ());

    e3dVector3 vEye  = camera.GetEye  ();
    e3dVector3 vLook = camera.GetLook ();
    e3dVector3 vUp   = camera.GetUp   ();

    //e3dVector3 vLook = vEye - vLook + center;
    //e3dVector3 vUp   = vEye - vUp   + center;


    e3dMatrix4x4 modelview;
//    modelview.LookAt (center, e3dVector3 (1.0f, 0.0f, 0.0f),  e3dVector3 (0.0f, 0.0f, 1.0f));
    //modelview.LookAt (center, e3dVector3 (-1.0f, 0.0f, 0.0f),  e3dVector3 (0.0f, 0.0f, 1.0f));
    modelview.LookAt (vEye, vLook, vUp);
    glLoadMatrixf (modelview.m);
    glTranslatef  (vEye.x - center.x, vEye.y - center.y, vEye.z - center.z);


//    glLoadMatrixf (camera.GetModelview ());

    //up.x = 0.0f; up.y = 0.0f; up.z = 1.0f;
//    light_pos = cli_parse::states->cl_player.Camera ()->GetPos ();

//    gluLookAt ( light_pos.x, light_pos.y, light_pos.z,
//                center.x,    center.y,    center.z,
//                up.x,        up.y,        up.z );
    //glGetFloatv (GL_MODELVIEW_MATRIX, lightViewMat);

//    lightViewMatrix       = e3dMatrix4x4 (lightViewMat);
//    lightProjectionMatrix = e3dMatrix4x4 (light_proj_mat);

    glDisable    (GL_CULL_FACE);
//    glDisable    (GL_ALPHA_TEST);
    glCullFace   (GL_BACK);
    glDepthMask  (GL_TRUE);
#ifndef DEBUG_SHADOWS
    glColorMask  (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
#endif
    glShadeModel (GL_FLAT);

//    glBindTexture (GL_TEXTURE_2D, shadow_tex);
//    glDisable     (GL_TEXTURE_2D);
}

void
EndDualShadows (int side)
{
  GLuint shadow_tex = InitDualShadows (side);

  //// Assumes the depth texture is already bound (ShadowPass must not change this...)
  glBindTexture       (GL_TEXTURE_2D, shadow_tex);

  glFinish  ();
  glCopyTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, 0, 0, shadow_res_x, shadow_res_y, 0);

  glClear (GL_DEPTH_BUFFER_BIT);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  glPopAttrib  ();

//  glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}

void
BeginDualShadowPostPass (e3dVector3 light_pos, int side)
{
    glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT); // Cull Face...

    static float white [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float grey  [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float black [4] = { 0.0f, 0.0f, 0.0f, 1.0f };

    /*
    double mvp [16];

    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
      glLoadTransposeMatrixf (lightViewMatrix.m);
      glMultTransposeMatrixf (lightProjectionMatrix.m);
      glGetDoublev           (GL_MODELVIEW_MATRIX, mvp);
   glPopMatrix ();


//    e3dMatrix4x4 mvp = biasMatrix * lightViewMatrix * lightProjectionMatrix;

    double clip_plane [4];

    clip_plane [0] = mvp [3] + mvp [2];
    clip_plane [1] = mvp [7] + mvp [6];
    clip_plane [2] = mvp [11] + mvp [10];
    clip_plane [3] = mvp [15] + mvp [14];

    glClipPlane (GL_CLIP_PLANE0, clip_plane);
    glEnable    (GL_CLIP_PLANE0);
    */

    glDepthMask (GL_FALSE);

    glColorMaterial      (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glColor4f            (0.1f, 0.1f, 0.1f, 1.0f);
    glEnable (GL_COLOR_MATERIAL);

 //Use dim light to represent shadowed areas
    glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
    glLightfv (GL_LIGHT1, GL_AMBIENT, grey);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, black);
    glDisable  (GL_LIGHT0);
    glEnable   (GL_LIGHT1);
    glEnable   (GL_LIGHTING);

    //3rd pass
    //Draw with bright light
    glLightfv (GL_LIGHT1, GL_DIFFUSE,  white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, white);


    //Calculate texture matrix for projection
    //This matrix takes us from eye space to the light's clip space
    //It is postmultiplied by the inverse of the current view matrix when specifying texgen

    /// TODO: Implement e3dMatrix4x4 construction from float params...

    static float tex_mat [16];

    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
      glLoadTransposeMatrixf (lightViewMatrix.m);
      glMultTransposeMatrixf (lightProjectionMatrix.m);
      glMultMatrixf (biasMatrix.m);
      glGetFloatv       (GL_MODELVIEW_MATRIX, tex_mat);
   glPopMatrix ();

   e3dMatrix4x4 textureMatrix (tex_mat);

//    e3dMatrix4x4 textureMatrix = biasMatrix * lightProjectionMatrix * lightViewMatrix;

//    textureMatrix = TransposeMat (textureMatrix);

    GLuint shadow_tex = InitDualShadows (side);

    //Bind & enable shadow map texture
    glBindTexture (GL_TEXTURE_2D, shadow_tex);
    glEnable      (GL_TEXTURE_2D);

#if 1
    //Set up texture coordinate generation.
    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_S, GL_EYE_PLANE, textureMatrix.Row (0));
    glEnable   (GL_TEXTURE_GEN_S);

    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_T, GL_EYE_PLANE, textureMatrix.Row (1));
    glEnable   (GL_TEXTURE_GEN_T);

    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_R, GL_EYE_PLANE, textureMatrix.Row (2));
    glEnable   (GL_TEXTURE_GEN_R);

    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv (GL_Q, GL_EYE_PLANE, textureMatrix.Row (3));
    glEnable   (GL_TEXTURE_GEN_Q);
#else
    //Set up texture coordinate generation.
//    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_S);

//    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_T);

//    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_R);

//    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_Q);
#endif

#ifndef DEBUG_SHADOWS
    //Enable shadow comparison
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);

    //Shadow comparison should be true (ie not in shadow) if r<=texture
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL);

    //Shadow comparison should generate an INTENSITY result
    glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
#endif

    //Set alpha test to discard false comparisons
    glAlphaFunc (GL_GEQUAL, 0.99f);
    glEnable    (GL_ALPHA_TEST);

    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendColor (0.33f, 0.33f, 0.33f, 1.0f);
    glBlendFunc (GL_DST_COLOR, GL_CONSTANT_COLOR);
//    glBlendFunc (GL_ZERO, GL_ZERO);
    glEnable    (GL_BLEND);

//    glDepthFunc (GL_LESS);
}

GLuint
InitOmniShadows (void)
{
  const int shadow_map_res = 512;

  static bool init       = false;
  static GLuint  shadow_tex = 0;

  /* IMPORTANT: For Cube Maps, dimensions MUST be a power of 2! */
#if 0
//#ifndef DIFFERENT_FOV
  /* Re-Initialize if the Size Changes. */
  if (shadow_res_x != ::states->window->viewport.Width  () ||
      shadow_res_y != ::states->window->viewport.Height ()) {
    if (init)
      glDeleteTextures (1, &shadow_tex);
    init = false;
  }

  shadow_res_x = ::states->window->viewport.Width  ();
  shadow_res_y = ::states->window->viewport.Height ();
#else
  shadow_res_x = 512;
  shadow_res_y = 512;
#endif

  if (init) {
    return shadow_tex;
  }

  cubemap_shadow_projector.init ();

  //Create the shadow map texture
  glGenTextures (1,                   &shadow_tex);
  glBindTexture (GL_TEXTURE_CUBE_MAP,  shadow_tex);

  glTexImage2D  ( GL_TEXTURE_CUBE_MAP, 0, GL_RGB, shadow_res_x, shadow_res_y, 0,
                  GL_RGBA, GL_UNSIGNED_BYTE, NULL);

  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);


#ifndef DEBUG_SHADOWS
 //Enable shadow comparison
//  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
 //Shadow comparison should be true (ie not in shadow) if r<=texture
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
 //Shadow comparison should generate an INTENSITY result
// glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
// glDisable (GL_TEXTURE_2D);
#endif

  if (shadow_tex != 0)
    init = true;

  return shadow_tex;
}


#include "light_cam.h"

/* Directional Light... */
void
BeginOmniShadows (e3dVector3 light_pos, Underlight::Room* room, int side)
{
  glEnable (GL_NORMALIZE);

  glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT | GL_VIEWPORT_BIT); // Cull Face...

  /* The Vertex Shader uses the Position of GL_LIGHT1... */
  glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
  glEnable  (GL_LIGHT1);
//  glEnable  (GL_LIGHTING);

  // Bind Shader


  glViewport (0, 0, shadow_res_x, shadow_res_y);

  glEnable        (GL_POLYGON_OFFSET_FILL);
//  glPolygonOffset (1.1f, 40.0f);
//  glClearDepth (1.0f);

  glEnable (GL_POLYGON_OFFSET_FILL);
  glPolygonOffset (1.1f, 4.0f);


//  glEnable        (GL_POLYGON_OFFSET_FILL);
//  glPolygonOffset (1.1f, 40.0f);
//  glClearDepth (1.0f);

//  glEnable (GL_POLYGON_OFFSET_FILL);
//  glPolygonOffset (0.9f, 5.0f);

  //glClear (GL_DEPTH_BUFFER_BIT);

  int shadow_tex = InitOmniShadows ();

//    GLfloat lightProjMat   [16];
//    GLfloat lightViewMat   [16];
//    GLfloat light_proj_mat [16];


    /* Only works for the case that the player is holding a spotlight straight out... */
/////    glLoadMatrixf (cli_parse::states->cl_player.Camera ()->GetModelview ());


    glMatrixMode   (GL_PROJECTION);
    glPushMatrix   ();
    glLoadIdentity ();
    gluPerspective (90.0f, 1.0f, 1.0f, 8000.0f);
    //glGetFloatv    (GL_PROJECTION_MATRIX, light_proj_mat);

    glMatrixMode   (GL_MODELVIEW);
    glPushMatrix   ();
    glLoadIdentity ();

    e3dVector3 up;
    e3dVector3 center;

    up.x = 0.0f; up.y = 0.0f; up.z = 1.0f;

    e3dCamera camera (*cli_parse::states->cl_player.Camera ());

    e3dVector3 vEye  = camera.GetEye  ();
    e3dVector3 vLook = camera.GetLook ();
    e3dVector3 vUp   = camera.GetUp   ();

    e3dVector3 vDif = vEye - center;

    e3dVector3 look;

    eTB_LightCam light_cam (light_pos, 90.0f, 1.0f, 1.0f, 8000.0f);

    const e3dVector3 PositiveLookX (1.0f, 0.0f, 0.0f);
    const e3dVector3 PositiveLookY (0.0f, 1.0f, 0.0f);
    const e3dVector3 PositiveLookZ (0.0f, 0.0f, 1.0f);

    const e3dVector3 NegativeLookX (-1.0f, 0.0f, 0.0f);
    const e3dVector3 NegativeLookY (0.0f, -1.0f, 0.0f);
    const e3dVector3 NegativeLookZ (0.0f, 0.0f, -1.0f);

    switch (side) {
      case 0:
        look = PositiveLookX;
        up   = PositiveLookY;
        break;
      case 1:
        look = NegativeLookX;
        up   = PositiveLookY;
        break;
      case 2:
        look = PositiveLookY;
        up   = NegativeLookZ;
        break;
      case 3:
        look = NegativeLookY;
        up   = NegativeLookZ;
        break;
      case 4:
        look = PositiveLookZ;
        up   = PositiveLookY;
        break;
      case 5:
        look = NegativeLookZ;
        up   = PositiveLookY;
        break;
    }

    e3dMatrix4x4 modelview;
    modelview.LookAt (center, look, up);

    glLoadMatrixf (modelview.m);

    //if (side < 2)
      //glColorMask (GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
    //else if (side < 4)
      //glColorMask (GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
    //else
      //glColorMask (GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);



    //center += cli_parse::states->cl_player.Camera ()->fwd_vec ();
    //center*= light_pos;
    //up     *= light_pos;

/////    light_pos = e3dVector3 (0.0f, 0.0f, 0.0f);

//    center.Normalize ();
//    center += light_pos;

    //center += light_pos;//proj->origin;//light_pos;
    //up     += light_pos;//proj->origin;//light_pos;

    //up = cli_parse::states->cl_player.Camera ()->up_vec ();



    //e3dMatrix4x4 mat;
    //mat.LookAt (light_pos, center, up);

    //glLoadMatrixf (cli_parse::states->cl_player.Camera ()->GetModelview ());

    //e3dCamera cam;
    //cam.LookAt (

    //gluLookAt ( light_pos.x, light_pos.y, light_pos.z,
    //            center.x,    center.y,    center.z,
    //            up.x,        up.y,        up.z );
    //glGetFloatv (GL_MODELVIEW_MATRIX, lightViewMat);

//    lightViewMatrix       = e3dMatrix4x4 (lightViewMat);
//    lightProjectionMatrix = e3dMatrix4x4 (light_proj_mat);

    glEnable    (GL_CULL_FACE);
//    glDisable    (GL_ALPHA_TEST);
    glCullFace   (GL_BACK);
    glDepthMask  (GL_TRUE);
#ifndef DEBUG_SHADOWS
    glColorMask  (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
#endif
    glShadeModel (GL_FLAT);

//    glBindTexture (GL_TEXTURE_2D, shadow_tex);
//    glDisable     (GL_TEXTURE_2D);
}

void
EndOmniShadows (int side)
{
  GLuint shadow_tex = InitOmniShadows ();

  //// Assumes the depth texture is already bound (ShadowPass must not change this...)
  glBindTexture       (GL_TEXTURE_CUBE_MAP, shadow_tex);

  glFinish  ();

  GLuint TexEnum = NULL;

    switch (side) {
      case 0:
        TexEnum = GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
        break;
      case 1:
        TexEnum = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
        break;
      case 2:
        TexEnum = GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
        break;
      case 3:
        TexEnum = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
        break;
      case 4:
        TexEnum = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
        break;
      case 5:
        TexEnum = GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
        break;
    }

    glCopyTexImage2D (TexEnum, 0, GL_RGB, 0, 0, shadow_res_x, shadow_res_y, 0);

  glClear (GL_DEPTH_BUFFER_BIT);

  glMatrixMode (GL_PROJECTION);
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  //cubemap_shadow_projector.build_depth_cube->Unbind ();

  glPopAttrib  ();
}

void
BeginOmniShadowPostPass (e3dVector3 light_pos)
{
    glPushAttrib (GL_POLYGON_BIT | GL_COLOR_BUFFER_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_TRANSFORM_BIT); // Cull Face...

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    static float white [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float grey  [4] = { 0.1f, 0.1f, 0.1f, 1.0f };
    static float black [4] = { 0.0f, 0.0f, 0.0f, 1.0f };

    glDepthMask (GL_FALSE);

    glColorMaterial      (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glColor4f            (0.1f, 0.1f, 0.1f, 1.0f);
    glEnable (GL_COLOR_MATERIAL);

 //Use dim light to represent shadowed areas
    glLightfv (GL_LIGHT1, GL_POSITION, light_pos);
    glLightfv (GL_LIGHT1, GL_AMBIENT, grey);
    glLightfv (GL_LIGHT1, GL_DIFFUSE, white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, black);
    glDisable  (GL_LIGHT0);
    glEnable   (GL_LIGHT1);
    glEnable   (GL_LIGHTING);

    //3rd pass
    //Draw with bright light
    glLightfv (GL_LIGHT1, GL_DIFFUSE,  white);
    glLightfv (GL_LIGHT1, GL_SPECULAR, white);


    //Calculate texture matrix for projection
    //This matrix takes us from eye space to the light's clip space
    //It is postmultiplied by the inverse of the current view matrix when specifying texgen

    /// TODO: Implement e3dMatrix4x4 construction from float params...

    /*
    static float tex_mat [16];

    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
      glLoadTransposeMatrixf (lightViewMatrix.m);
      glMultTransposeMatrixf (lightProjectionMatrix.m);
      glMultMatrixf (biasMatrix.m);
      glGetFloatv       (GL_MODELVIEW_MATRIX, tex_mat);
   glPopMatrix ();

   e3dMatrix4x4 textureMatrix (tex_mat);
   */

//    e3dMatrix4x4 textureMatrix = biasMatrix * lightProjectionMatrix * lightViewMatrix;

//    textureMatrix = TransposeMat (textureMatrix);

    GLuint shadow_tex = InitOmniShadows ();

    //Bind & enable shadow map texture
    glBindTexture (GL_TEXTURE_CUBE_MAP, shadow_tex);
    glDisable     (GL_TEXTURE_2D);
    glEnable      (GL_TEXTURE_CUBE_MAP);

#if 0
    glTexGenf (GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGenf (GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGenf (GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glEnable   (GL_TEXTURE_GEN_S);
    glEnable   (GL_TEXTURE_GEN_T);
    glEnable   (GL_TEXTURE_GEN_R);

    //Set up texture coordinate generation.
//    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
//    glTexGenfv (GL_S, GL_EYE_PLANE, textureMatrix.Row (0));
//    glEnable   (GL_TEXTURE_GEN_S);

//    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
//    glTexGenfv (GL_T, GL_EYE_PLANE, textureMatrix.Row (1));
//    glEnable   (GL_TEXTURE_GEN_T);

//    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
//    glTexGenfv (GL_R, GL_EYE_PLANE, textureMatrix.Row (2));
//    glEnable   (GL_TEXTURE_GEN_R);

//    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
//    glTexGenfv (GL_Q, GL_EYE_PLANE, textureMatrix.Row (3));
//    glEnable   (GL_TEXTURE_GEN_Q);
#else
    //Set up texture coordinate generation.
//    glTexGeni  (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_S);

//    glTexGeni  (GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_T);

//    glTexGeni  (GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_R);

//    glTexGeni  (GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
//    glEnable   (GL_TEXTURE_GEN_Q);
#endif

#ifndef DEBUG_SHADOWS
    //Enable shadow comparison
//    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);

    //Shadow comparison should be true (ie not in shadow) if r<=texture
//    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL);

    //Shadow comparison should generate an INTENSITY result
//    glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
#endif

    //Set alpha test to discard false comparisons
    glAlphaFunc (GL_GEQUAL, 0.99f);
    glEnable    (GL_ALPHA_TEST);

#if 0
    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBlendColor (0.33f, 0.33f, 0.33f, 1.0f);
    glBlendFunc (GL_DST_COLOR, GL_CONSTANT_COLOR);
//    glBlendFunc (GL_ZERO, GL_ZERO);
    glEnable    (GL_BLEND);

    glDisable (GL_TEXTURE_CUBE_MAP);
#else
   glDisable (GL_BLEND);
   glDisable (GL_TEXTURE_GEN_Q);
   glDisable (GL_TEXTURE_GEN_R);
   glDisable (GL_TEXTURE_GEN_S);

   glEnable (GL_TEXTURE_CUBE_MAP);
   glBindTexture (GL_TEXTURE_CUBE_MAP, shadow_tex);
#endif

//    glDepthFunc (GL_LESS);
}

void
EndOmniShadowPostPass (void)
{
  glPopAttrib ();

  glDepthMask (GL_TRUE);
  glDisable   (GL_CLIP_PLANE0);

  GLuint error = glGetError ();
  if (error != GL_NO_ERROR) {
    eTB_Printf ("OpenGL Error : %x (%d)\n", error, error);
  }
}



#include "room.h"




